import Env from './Env.js';
export default class OkxWsPublicService {
    /**
     * Singleton instance of OkxWsPublicService
     * @type {OkxWsPublicService}
     * @static
     */
    static instance = null;

    /**
     * Get the singleton instance of OkxWsPublicService
     * @returns {OkxWsPublicService}
     * @static
     */
    static getInstance() {
        if (OkxWsPublicService.instance === null) {
            OkxWsPublicService.instance = new OkxWsPublicService();
        }
        return OkxWsPublicService.instance;
    }

    /**
     * Create an instance of OkxWsPublicService
     * @constructor
     * @private
     */
    constructor() {
        this.address = Env.OKX_WS_PUBLIC_ADDRESS;

        this.ws = null;
        this.subscriptions = {};
        this.subscriptionPromise = null;
        this.unsubscriptionPromise = null;
    }

    /**
     * start the WebSocket connection
     * @returns {void}
     */
    async start() {
        return new Promise((resolve, reject) => {
            this.ws = new WebSocket(this.address);
            this.ws.onmessage = (event) => this.handleOnMessage(event);
            this.ws.onopen = () => {
                this.log('WebSocket Connected');
                resolve();
            };
            this.ws.onerror = (err) => {
                console.error('WebSocket error:', err);
            };
            this.ws.onclose = () => {
                console.log('WebSocket closed');
            };
        });
    }

    stop() {}

    /**
     * subscribe to a channel
     * @param {*} channel
     * @param {*} args
     * @param {*} callback
     */
    subscribe( channel, args, callback ) {
        return new Promise(( resolve, reject ) => {
            if (this.subscriptions[channel]) {
                this.subscriptions[channel].push(callback);
            } else {
                this.subscriptions[channel] = [callback];
            }

            args.channel = channel;
            const subscribeMsg = {op:'subscribe',args: [args]};
            this.ws.send(JSON.stringify(subscribeMsg));
            this.subscriptionPromise = {resolve, reject};
        });
    }

    /**
     * unsubscribe from a channel
     * @param {*} channel
     * @param {*} args
     */
    unsubscribe( channel, args ) {
        return new Promise(( resolve, reject ) => {
            if (this.subscriptions[channel]) {
                delete this.subscriptions[channel];
            }
            args.channel = channel;
            const unsubscribeMsg = {op:'unsubscribe',args: [args]};
            this.ws.send(JSON.stringify(unsubscribeMsg));
            this.unsubscriptionPromise = {resolve, reject};
        });
    }

    /**
     * event handler for when a message is received
     * @param {*} event
     * @private
     */
    handleOnMessage( event ) {
        const data = JSON.parse(event.data);
        if ( data.event === 'subscribe') {
            if (this.subscriptionPromise) {
                this.subscriptionPromise.resolve();
                this.subscriptionPromise = null;
                this.log('Subscription successful :', data.arg);
            }
        } else if ( 'unsubscribe' === data.event ) {
            if (this.unsubscriptionPromise) {
                this.unsubscriptionPromise.resolve();
                this.unsubscriptionPromise = null;
                this.log('Unsubscription successful:', data.arg);
            }
        } else {
            let channel = data.arg.channel;
            let callbacks = this.subscriptions[channel] || [];
            callbacks.forEach(callback => callback(data.data, data.arg));
        }
    }

    /**
     * log messages to the console
     * @param  {...any} args
     * @private
     */
    log( ... args ) {
        console.log( '[OkxWsPublicService]', ... args );
    }
}
